package datastructures;

import java.io.FileWriter;
import java.io.IOException;
import java.util.StringTokenizer;

public class UserParameters {
	
	private String usage = "[ USAGE2 : ] \n" + 
			"\t See software webpage for complete usage documentation \n" +
			"\t java -Xmx2G -jar Sixpac.jar \n\n" +
			
			"[  DATA INPUTS : ] \n" + 
			"\t --raw [raw file name] \t\t { OR } \t\t   --cases [raw file name] --controls [raw file name]  \n" + 
			"\t --map [map file name]  \n" + 
			"\t --pheno [phenotype file name]  \n" + 
			"\t --out [output file prefix] (output file will always have the .sxp extension \n\n" +		
			
			"[  FUNCTION INPUTS : ] \n" + 
			"\t --mode [complete, approx, pac, test, random] \n" + 
			"\t (Default is a pac search for bonferroni effects on input dataset with 10% power)\n" +
			"\t (complete, approx, pac) are slowest, slow and fast search modes respectively - they scan the dataset for interactions \n" +
			"\t (test) reads in a list of SNP pairs provided in another file (see below) and outputs the LD-contrast pvalue of those pairs \n"+
			"\t (random) draws random pairs of SNPs from the dataset and computes their p-value (useful for drawing QQ plots) \n\n" +
			
			"[ FUNCTION OPTIONS : ] \n" +
			"\t --power [number between 0 and 1] \n" +
			"\t --trials [maximum number of computational trials per filter] \n" + 
//			"\t --lenience [factor between 1 and 10^6 to which Bonferroni might be relaxed for stage 1] \n" + 
			"\t --significance [multiple correction threshold p-value] - (default is bonferroni)\n" + 
			"\t --seed [random seed (1 to 100)] \n" +
			"\t --verbose [0 or 1 or 2]\n" +
			"\t--prevalence [disease incidence in population between 0 and 1]\n" + 
//			"\t --order [maximum order of interaction to search (upto 2)] \n" +
//			"\t --blocks [haplotype block file name]    { OR }   " +
			"\t --useblocks-bp [size of LD block in bp]   {OR}   --useblocks-cm [size of LD block in cm] \n" +
			"\t --in-cis {OR} --in-trans \n" +
			"\t --flip-pheno \n\n" +
			
	
			"[ Non-search Mode Options : ] \n" +
			"\t --testfile [interaction pairs to test] \n" +
			"\t --span [bp around each entry in test file] \n" +
			"\t --draws [number of random pairs to draw] \n" +
			"\t --synergy-only \n" +
			"\t --avoid [file containing SNPs to leave out of analysis]\n\n" + 
 			

"[ PERFORMANCE RELATED INPUTS ] : \n" +
			"\t --threads [number of threads to parallelize sampling with] \n" +
			"\t --maxheap [Heap space in MB to reserve per thread] \n";
//			"\t--penetrance [penetrance of multi-locus genotype between 0 and 1]" +
		
	
	private String fileprefix;
	private String mapfilename;
	private String frqfilename;
	private String pedfilename;
	private String phenofilename;
	private String geneticMapfilename;
	
	private String allRawfilename;
	private String caseRawfilename;
	private String controlRawfilename;
	
	private String significancefile;
	private String stage1output;
	private String stage2output;
	private String blockfile;
	private String avoidfile;
	private String testfile;
	private String mode;
	
	private int number_of_snps;
	private int number_of_cases;
	private int number_of_controls;
	
	private float f_hi;
	private float f_low;
	private float cooccurrence;
	private long T_max;
	private float prevalence;
	private float power;
	private int verbose;
	private double minPenetrance;
	private int maxorder;
	private int maxThreads;
	private int maxHeapPerThread;
	private float blockSizebp;
	private float blockSizecm;
	private double randomSeed;
	private int minHits;
	private int lenience;
	private double significance;
	private double stage1CandidatesToOutput;
	private double slope;
	private double intercept;
	private int span;
	private long numberOfDraws;
	private int AllOrTransOrCis;
	private boolean synergyOnly;
	private String testName;
	
	private final int trialIncrement;
	private int numberOfTrialSlices;
	private int freqWindows;
	private int oneTooManyCases;
	private int oneTooManyControls;
	private double p_lowest;
	private int marginals;
	private double marginal_threshold;

	private UserParameters() 
	{
		//defaults
		this.fileprefix = "";
		this.mapfilename = "";
		this.phenofilename = "";
		this.frqfilename = "";
		this.pedfilename = "";
		this.caseRawfilename = "";
		this.controlRawfilename = "";
		this.allRawfilename = "";
		this.geneticMapfilename = "";
		this.stage1output = "sixpac";
		this.stage2output = "sixpac.sxp";
		this.significancefile = "sixpac";
		this.number_of_cases = 20;
		this.number_of_controls = 20;
		this.number_of_snps = 500000;
		this.avoidfile = "";
		this.testfile = "";
		this.verbose = 1;
		this.slope = 1;
		this.span = 0;
		this.numberOfDraws = 0;
		this.AllOrTransOrCis = 0; //default all
		this.synergyOnly = false;
		this.intercept = 0;		
		this.mode = "pac";
		this.testName="R";
		
		//user specifiable
		this.f_low = (float) 0;
		this.f_hi = (float) 0.5;
		this.cooccurrence = (float) 1;
		this.T_max = 10000000; 
		this.trialIncrement = 1000;
		this.numberOfTrialSlices = (int) Math.ceil(T_max / trialIncrement);
		this.prevalence = (float) 0.04;
		this.power = (float) 0.1;
		this.lenience = 1; 
		this.significance = 1; 
		this.minPenetrance = prevalence + 1e-5;
		this.maxorder = 2;
		this.maxThreads = Runtime.getRuntime().availableProcessors(); 
		this.maxHeapPerThread = 300;
		this.blockSizebp = 1;
		this.blockSizecm = -1;
		this.freqWindows = 79;
		this.randomSeed = 100000000 * Math.random();
		this.marginals = 5;
		this.marginal_threshold = 5e-08;
		this.minHits = 1;
		this.stage1CandidatesToOutput = 0; //default, don't output stage1 candidates
				
		//inferred
		this.oneTooManyCases = number_of_cases + 1;
		this.oneTooManyControls = number_of_controls + 1;
		this.p_lowest =  1 - Math.exp ( Math.log(1 - power) / T_max ); // (1-p)^T < uncertainty;
	}


	public int getMaxThreads() {
		return maxThreads;
	}

	public int getMaxHeapPerThread() {
		return maxHeapPerThread;
	}
	
	public void setMaxThreads(int maxThreads) {
		this.maxThreads = maxThreads;
	}

	public void setMaxHeapPerThread(int maxHeapPerThread) {
		this.maxHeapPerThread = maxHeapPerThread;
	}

	public void setNumber_of_snps(int numberOfSnps) {
		number_of_snps = numberOfSnps;
	}


	public void setNumber_of_cases(int numberOfCases) {
		number_of_cases = numberOfCases;
		oneTooManyCases = numberOfCases + 1;
	}


	public void setNumber_of_controls(int numberOfControls) {
		number_of_controls = numberOfControls;
		oneTooManyControls = numberOfControls + 1;
	}


	public UserParameters( String[] args ) {
		this();
		
		if( args.length < 2 ) {
			System.out.println(usage);
			System.exit(0);
		}

		System.out.println("\nRunning with inputs : ");
		
		for(int index=0; index<args.length; index++) {
			
			if(args[index].equals("--raw")) {
				index++;
				this.allRawfilename = args[index];
				System.out.println("\t --raw " + args[index]);
				continue;
			}
			
			if(args[index].equals("--cases")) {
				index++;
				this.caseRawfilename = args[index];
				System.out.println("\t --cases " + args[index]);
				continue;
			}
			
			if(args[index].equals("--controls")) {
				index++;
				this.controlRawfilename = args[index];
				System.out.println("\t --controls " + args[index]);
				continue;
			}
			
			//providing a separate output destination
			if(args[index].equals("--out")) {
				index++;
				this.stage1output = args[index]+".sxp.stage1";
				this.stage2output = args[index]+".sxp";
				this.significancefile = args[index]+".significance";
				System.out.println("\t --out " + args[index]);
				continue;
			}
			
			if(args[index].equals("--map")) {
				index++;
				this.mapfilename = args[index];
				System.out.println("\t --map " + args[index]);
				continue;
			}
			
			if(args[index].equals("--pheno")) {
				index++;
				this.phenofilename = args[index];
				System.out.println("\t --pheno " + args[index]);
				continue;
			}
			
			if(args[index].equals("--freq")) {
				index++;
				this.frqfilename = args[index];
				System.out.println("\t --freq " + args[index]);
				continue;
			}
			
			if(args[index].equals("--avoid")) {
				index++;
				this.avoidfile = args[index];
				System.out.println("\t --avoid " + args[index]);
				continue;
			}
			
			/*
			 * complete, approx, pac, test, random
			 */
			if(args[index].equals("--mode")) {
				index++;
				this.mode = args[index];
				System.out.println("\t --mode " + args[index]);
				continue;
			}

			/*
			 * 0 for brief, 1 for normal (default), or 2 for verbose 
			 */
			if(args[index].equals("--verbose")) {
				index++;
				this.verbose = Integer.parseInt(args[index]);
				System.out.println("\t --verbose " + args[index]);
				continue;
			}
			
			/*
			 * separate LD block file from something like haploview.
			 */
			if(args[index].equals("--useblockfile")) {
				index++;
				this.blockfile = args[index];
				System.out.println("\t --useblockfile " + args[index]);
				continue;
			}
			
			if(args[index].equals("--testfile")) {
				index++;
				this.testfile = args[index];
				System.out.println("\t --testfile " + args[index]);
				continue;
			}
			
			if(args[index].equals("--testname")) {
				index++;
				this.testName = args[index];
				System.out.println("\t --testname " + args[index]);
				continue;
			}
			
			
			if(args[index].equals("--genetic-map")) {
				index++;
				this.geneticMapfilename = args[index];
				System.out.println("\t --genetic-map " + args[index]);
				continue;
			}
			
			if(args[index].equals("--span")) {
				index++;
				this.span = Integer.parseInt( args[index] );
				System.out.println("\t --span " + args[index]);
				continue;
			}
			
			if(args[index].equals("--draws")) {
				index++;
				this.numberOfDraws = Long.parseLong( args[index] );
				System.out.println("\t --draws " + args[index]);
				continue;
			}
			
			if(args[index].equals("--in-trans") ) {
				this.AllOrTransOrCis = 1;
				System.out.println("\t --in-trans");
				continue;
			}
			
			if(args[index].equals("--in-cis") ) {
				this.AllOrTransOrCis = 2;
				System.out.println("\t --in-cis");
				continue;
			}
			
			if(args[index].equals("--synergy-only") ) {
				this.synergyOnly = true;
				System.out.println("\t --synergy-only");
				continue;
			}
			
			if(args[index].equals("--fhi")) {
				index++;
				this.f_hi = Float.parseFloat( args[index] );
				System.out.println("\t --fhi " + args[index]);
				continue;
			}
			
			if(args[index].equals("--flow")) {
				index++;
				this.f_low = Float.parseFloat( args[index] );
				System.out.println("\t --flow " + args[index]);
				continue;
			}
			
			//max co-occurence frequency of multi-locus genotype
			if(args[index].equals("--rare")) {
				index++;
				this.cooccurrence = Float.parseFloat( args[index] );
				System.out.println("\t --rare " + args[index]);
				continue;
			}
			
			
			// number of iterations
			if(args[index].equals("--trials")) {
				index++;
				this.T_max = Long.parseLong(args[index]);
				this.numberOfTrialSlices = (int) Math.ceil(T_max / trialIncrement);
				System.out.println("\t --trials " + args[index]);
				continue;
			}
			
			
			//prevalence of disease in population
			if(args[index].equals("--prevalence")) {
				index++;
				this.prevalence = Float.parseFloat( args[index] );
				System.out.println("\t --prevalence " + args[index]);
				continue;
			}
			
			
			// sensitivity / power level.
			if(args[index].equals("--power")) {
				index++;
				this.power = Float.parseFloat( args[index] );
				System.out.println("\t --power " + args[index]);
				continue;
			}
			
			
			//random seed (to replicate run)
			if(args[index].equals("--seed")) {
				index++;
				this.randomSeed = Float.parseFloat( args[index] );
				System.out.println("\t --seed " + args[index]);
				continue;
			}
			
			// min proportion of multi-locus carriers that develop disease.
			if(args[index].equals("--penetrance")) {
				index++;
				this.minPenetrance = Double.parseDouble( args[index] );
				System.out.println("\t --penetrance " + args[index]);
				continue;
			}
			
			if(args[index].equals("--threads")) {
				index++;
				this.maxThreads = Math.min( Integer.parseInt(args[index]), //as many as user defines
						Runtime.getRuntime().availableProcessors() //consider cautioning against hyperthreaded Intel quadcores showing up as 8 cores
						); 
				this.maxHeapPerThread = 2000/this.maxThreads;
//				System.out.println("*WARNING* Hyperthreading technology may cause poor performance! ");
//				System.out.println("*WARNING* For best performance, restrict #threads to #physical processing cores.\n");
//				System.out.println("Using " + this.maxThreads + " threads.");
				System.out.println("\t --threads " + args[index]);
				continue;
			}
			
			if(args[index].equals("--maxheap")) {
				index++;
				this.maxHeapPerThread = (int) Math.abs( Integer.parseInt( args[index] ) );
				System.out.println("\t --maxheap " + args[index]);
				continue;
			}
			
			/*
			 * GPD-contrast p-value cutoff specified by the user ( Can be used to override default Bonferroni ).
			 */
			if(args[index].equals("--significance")) {
				index++;
				this.significance = Math.abs( Double.parseDouble( args[index] ) );
				System.out.println("\t --significance " + args[index]);
				continue;
			}
			
			/*
			 * number of stage1 candidates to output
			 */
			if(args[index].equals("--stage1")) {
				index++;
				this.stage1CandidatesToOutput = Double.parseDouble(args[index]);
				System.out.println("\t --stage1 " + args[index]);
				continue;
			}
			
			/*
			 * interaction arity (k)
			 */
			if(args[index].equals("--order")) {
				index++;
				this.maxorder = Integer.parseInt( args[index] );
				System.out.println("\t --order " + args[index]);
				continue;
			}
			
			// number of times a combination needs to be observed in stage 1 for candidacy in stage 2 follow-up 
			// (not applicable for this implementation - all hits followed up)
			if(args[index].equals("--minhits")) {
				index++;
				this.minHits = Integer.parseInt( args[index] );
				if (minHits < 1)
					minHits = 1;
				System.out.println("\t --minhits " + args[index]);
				continue;
			}
			
			// number of frequency windows (fine-grainedness) to use.
			if(args[index].equals("--windows")) {
				index++;
				this.freqWindows = Integer.parseInt( args[index] );
				System.out.println("\t --windows " + args[index]);
				continue;
			}
			
			// size of LD blocks in bp to use. Pairs within a single block/window will not be considered.
			if(args[index].equals("--useblocks-bp")) {
				index++;
				this.blockSizebp = Float.parseFloat(args[index]);
				System.out.println("\t --useblocks-bp " + args[index]);
				continue;
			}
			
			// size of LD blocks in cm to use. Pairs within a single block/window will not be considered. MAP file must contain cm distance column.
			if(args[index].equals("--useblocks-cm")) {
				index++;
				this.blockSizecm = Float.parseFloat(args[index]);
				System.out.println("\t --useblocks-cm " + args[index]);
				continue;
			}
			
			// upto these many contributing SNPs in the interaction have a marginal effect
			if(args[index].equals("--marginals")) {
				index++;
				this.marginals = Integer.parseInt(args[index]);
				System.out.println("\t --marginals " + args[index]);
				continue;
			}
			
			// this is the threshold below which they are considered marginal (see above)
			if(args[index].equals("--threshold")) {
				index++;
				this.marginal_threshold = Float.parseFloat(args[index]);
				System.out.println("\t --threshold " + args[index]);
				continue;
			}
			
			// this is the threshold below which they are considered marginal (see above)
			if(args[index].equals("--correction")) {
				index++;
				StringTokenizer linearRegrParams = new StringTokenizer(args[index],",");
				if (linearRegrParams.countTokens() != 2) {
					System.out.println("Format : [slope],[intercept]");
					System.exit(1);
				}				
				this.slope = Double.parseDouble(linearRegrParams.nextToken());
				this.intercept = Double.parseDouble(linearRegrParams.nextToken());
				continue;
			}
			
			//default
			System.err.println("Invalid option \"" + args[index] + "\"");
			System.out.println(usage);
			System.exit(1);
		}

		System.out.println();
		
		processInputs();
		
	}

	
	private void processInputs() {

		//1. First reset the file
		try {
			if (stage1CandidatesToOutput != 0) {
				FileWriter f1 = new FileWriter(stage1output); //no header policy
				f1.close();
			}
			
			FileWriter f2 = new FileWriter(stage2output);
			f2.flush();
			
			String header = "SNP-1 \t SNP-2 \t " +
					"Expected[Cases] \t Observed[Cases] \t p-val(Case-only) \t " +
					"Expected[Controls] \t Observed[Controls] \t p-val(Case-Control) \t " +
					"D'(cases) \t D'(controls) \n"; 
			
			if (this.verbose == 0) 
				header = "SNP-1 \t SNP-2 \t " +
				"D'(cases) \t p-val(Case-only) \t " +
				"D'(controls) \t p-val(Case-Control) \n"; 
			
			if(this.verbose == 2)
				header = "SNP-1 \t SNP-2 \t " +
				"Expected[Cases] \t Observed[Cases] \t p-val(Case-only) \t " +
				"Expected[Controls] \t Observed[Controls] \t p-val(Case-Control) \t " +
				"D'(cases) \t D'(controls) \t" + 
				"Case-Carriers \t Control-Carriers \n"; 
			
			//if you aren't writing the matrix format
			if ( this.span == 0) {
				f2.write(header);
			}
			
			f2.close();
			
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		//2. calculate the lowest p that you can flatten to based on sensitivity and permissible Tmax
		this.p_lowest = 1 - Math.exp( Math.log(1 - power) / T_max ); // (1-p)^T < uncertainty
		
	}
	
	
	public String getFilePrefix() {
		return fileprefix;
	}

	public String getMapfilename() {
		return mapfilename;
	}
	
	public String getPhenofilename() {
		return phenofilename;
	}

	public String getFrqfilename() {
		return frqfilename;
	}

	public String getPedfilename() {
		return pedfilename;
	}

	public String getAvoidfilename() {
		return avoidfile;
	}
	
	public String getTestfilename() {
		return testfile;
	}
	
	public String getTestName() {
		return testName;
	}
	
	public String getGeneticMapFile() {
		return geneticMapfilename;
	}
	
	public int getSpan() {
		return span;
	}

	public boolean getSynergyOnly() {
		return synergyOnly;
	}
	
	public long getNumberOfDraws() {
		return numberOfDraws;
	}
	
	public int getAllOrTransOrCis() {
		return AllOrTransOrCis;
	}
	
	public String getCasefilename() {
		return caseRawfilename;
	}

	public String getControlfilename() {
		return controlRawfilename;
	}
	
	public String getSensitivityfilename() {
		return significancefile;
	}
	
	public String getStage1Output() {
		return stage1output;
	}

	public String getStage2Output() {
		return stage2output;
	}
	
	public String getBlockfilename() {
		return blockfile;
	}

	public String getMode() {
		return mode;
	}
	
	public int getNumber_of_snps() {
		return number_of_snps;
	}

	public int getNumber_of_cases() {
		return number_of_cases;
	}

	public int getNumber_of_controls() {
		return number_of_controls;
	}

	public float getF_low() {
		return f_low;
	}
	
	public float getF_hi() {
		return f_hi;
	}
	
	public int getNumberOfTrialSlices() {
		return numberOfTrialSlices;
	}
	
	public int getTrialIncrement() {
		return trialIncrement;
	}
	
	public float getCooccurrence() {
		return cooccurrence;
	}
	
	public long getT_max() {
		return T_max;
	}
	
	public float getPrevalence() {
		return prevalence;
	}
	
	public double getPenetrance() {
		return minPenetrance;
	}

	public float getPower() {
		return power;
	}

	public int getMaxorder() {
		return maxorder;
	}
	
	public int getLenience() {
		return lenience;
	}

	public double getSignificanceThreshold() {
		return significance;
	}
	
	public int getMinHits() {
		return minHits;
	}
	
	public double getRandomSeed() {
		return randomSeed;
	}

	public int getFreqWindows() {
		return freqWindows;
	}

	public float getBlockSizeBp() {
		return blockSizebp;
	}
	
	public float getBlockSizeCm() {
		return blockSizecm;
	}

	public double getSlope() {
		return slope;
	}


	public void setSlope(double slope) {
		this.slope = slope;
	}


	public double getIntercept() {
		return intercept;
	}


	public void setIntercept(double intercept) {
		this.intercept = intercept;
	}

	
	public int getOneTooManyCases() {
		return oneTooManyCases;
	}

	public int getOneTooManyControls() {
		return oneTooManyControls;
	}
	
	public double getP_lowest() {
		return p_lowest;
	}

	public int getMarginals() {
		return marginals;
	}

	public double getMarginal_threshold() {
		return marginal_threshold;
	}
	
	public String getAllRawfilename() {
		return allRawfilename;
	}

	public int getVerboseState() {
		return verbose;
	}
	
	public double howManyStage1CandidatesToOutput() {
		return stage1CandidatesToOutput;
	}

	public void setstage1CandidatesToOutput(int howManyStage1CandidatesToOutput) {
		this.stage1CandidatesToOutput = howManyStage1CandidatesToOutput;
	}
	
}
